add Settings domain with DB persistence and admin CRUD API#939
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a persistent, runtime-configurable Settings domain with Get/Update/Clear admin APIs, DB migrations and repo implementations (badger/sqlite/postgres), wiring to load/apply settings at startup and runtime, plus docs, Makefile, env and e2e test updates to use admin-managed settings. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant GRPC as "gRPC Handler"
participant AdminApp as "Admin Service"
participant RepoMgr as "Repo Manager"
participant SettingsRepo as "Settings Repo"
participant AppSvc as "App Service"
Client->>GRPC: UpdateSettings(request)
GRPC->>AdminApp: UpdateSettings(settings)
AdminApp->>AdminApp: validate & set UpdatedAt
AdminApp->>RepoMgr: Settings()
RepoMgr-->>AdminApp: SettingsRepository
AdminApp->>SettingsRepo: Upsert(settings)
SettingsRepo->>SettingsRepo: persist to storage
SettingsRepo-->>AdminApp: OK
AdminApp->>AppSvc: onSettingsUpdated / UpdateSettings(settings)
AppSvc->>AppSvc: recalc derived values & tapscript
AppSvc-->>AdminApp: OK
AdminApp-->>GRPC: UpdateSettingsResponse
GRPC-->>Client: Response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…-and-repo # Conflicts: # api-spec/protobuf/gen/ark/v1/indexer.pb.rgw.go
# Conflicts: # internal/config/config.go
🔍 Arkana PR Review — arkd#939: Settings Domain with DB PersistenceScope: Moves 16 runtime-tunable config values (ban threshold/duration, exit delays, VTXO tree expiry, round participant limits, amount limits, etc.) from environment variables into a DB-persisted Settings domain with admin CRUD API. Touches 38 files, +2413/−199 lines. Overall: Well-structured domain model following the existing repository pattern. Clean separation across Postgres, SQLite, and Badger backends with proper migrations. Good test coverage. A few concerns below, some security-relevant. 🔴 Security: No Input Validation on UpdateSettings
Risks:
Suggestion: Add a Also consider using proto 🟡 Bug: ClearSettings Doesn't Reset Running Service
func (a *adminService) ClearSettings(ctx context.Context) error {
if err := a.repoManager.Settings().Clear(ctx); err != nil {
return err
}
// Missing: reset service to default settings
a.onInfoChange()
return nil
}Should either: (1) re-seed defaults and call 🟡 Duplicated Locktime Logic
Suggestion: Extract the shared logic into a single function in 🟢 Positives
Minor Notes
Verdict: The architectural approach is solid and the implementation is thorough. The missing input validation on UpdateSettings is the most critical item — it's a live admin API that directly controls protocol-security parameters (exit delays, VTXO expiry). Would recommend addressing the validation gap before merge. |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (5)
Makefile (2)
184-200: Remove staleMIN/MAXhandling fromrun-simulationoutput and shell vars.Line 197-Line 199 compute/log
MINandMAX, but they are never passed to the test command. Ininternal/test/e2e/single_batch_smoke_test.go(Line 23-Line 65), only-num-clientsis parsed, while min/max participants are hardcoded in test config. This log is misleading.Proposed cleanup
`@bash` -c '\ CLIENTS="$${CLIENTS:-5}"; \ - MIN="$${MIN:-$$CLIENTS}"; \ - MAX="$${MAX:-128}"; \ - echo "Running batch settlement test with $$CLIENTS clients (MIN=$$MIN, MAX=$$MAX)..."; \ + echo "Running batch settlement test with $$CLIENTS clients..."; \ go test -v -count=1 -timeout 1200s github.com/arkade-os/arkd/internal/test/e2e -run TestBatchSettleMultipleClients -args -smoke -num-clients=$$CLIENTS; \ '🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Makefile` around lines 184 - 200, The run-simulation Makefile target prints and sets unused MIN and MAX variables which is misleading; remove the MIN and MAX assignments and their use in the echo so the target only reads CLIENTS and passes -num-clients to the test. Update the run-simulation recipe's shell block to drop MIN="$${MIN:-$$CLIENTS}" and MAX="$${MAX:-128}" and change the echo to only reference $$CLIENTS (leaving the go test invocation that runs TestBatchSettleMultipleClients unchanged); this aligns the Makefile with the test in internal/test/e2e/single_batch_smoke_test.go which only parses -num-clients.
191-192: MakeARKD_SESSION_DURATIONoverrideable while keeping default60.Line 192 hardcodes
ARKD_SESSION_DURATION=60. Consider defaulting to 60 but allowing callers to override (make run-simulation ARKD_SESSION_DURATION=120) for troubleshooting slower environments.Proposed tweak
- `@ARKD_SESSION_DURATION`=60 docker compose -f docker-compose.regtest.yml up --build -d + `@ARKD_SESSION_DURATION`=$${ARKD_SESSION_DURATION:-60} docker compose -f docker-compose.regtest.yml up --build -d🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Makefile` around lines 191 - 192, The recipe currently hardcodes ARKD_SESSION_DURATION=60 in the Docker command; make this default-but-overridable by either (a) adding a Makefile variable declaration like ARKD_SESSION_DURATION ?= 60 and using ARKD_SESSION_DURATION in the recipe, or (b) using shell expansion in the recipe ARKD_SESSION_DURATION=${ARKD_SESSION_DURATION:-60} docker compose ... so callers can run make run-simulation ARKD_SESSION_DURATION=120 to override while keeping 60 as the default.internal/core/application/service.go (1)
304-349: Consider thread safety for runtime settings updates.
UpdateSettingsmodifies multiple service fields that may be read concurrently by round processing goroutines (e.g.,banDuration,roundMinParticipantsCount,checkpointExitDelay). Without synchronization, this could cause data races.Options to consider:
- Document that settings changes take effect between rounds (eventual consistency)
- Add a mutex to protect settings reads/writes
- Use atomic operations for individual fields
If the current behavior (unsynchronized updates) is intentional for simplicity, consider adding a comment documenting when it's safe to call this method.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/application/service.go` around lines 304 - 349, The UpdateSettings method mutates many runtime fields (e.g., banDuration, banThreshold, unilateralExitDelay, publicUnilateralExitDelay, checkpointExitDelay, boardingExitDelay, batchExpiry, roundMinParticipantsCount, roundMaxParticipantsCount, vtxoMinAmount, vtxoMaxAmount, utxoMinAmount, utxoMaxAmount, settlementMinExpiryGap, vtxoNoCsvValidationCutoffTime, maxTxWeight, checkpointTapscript, forfeitPubkey) without synchronization; add a sync.RWMutex (e.g., settingsMu) to the service struct and wrap the entire UpdateSettings body with settingsMu.Lock()/Unlock(), and update all readers of those fields (round processing goroutines, any methods reading these fields) to use settingsMu.RLock()/RUnlock(); alternatively, if unsynchronized updates are intentional, add a clear comment on UpdateSettings documenting that it must only be called between rounds and is not concurrency-safe.internal/core/domain/settings.go (1)
25-55: Avoid the 16-argument positional constructor.All of these parameters are
int64, so a single swap still compiles and silently assigns the wrong setting. A dedicated input struct (or plain struct literal at call sites) is much safer to review and extend.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/domain/settings.go` around lines 25 - 55, The NewSettings function takes 16 positional int64 arguments which risks silent swaps; replace it with a single explicit config struct (e.g. SettingsConfig or SettingsOptions) containing named fields matching Settings (BanThreshold, BanDuration, UnilateralExitDelay, PublicUnilateralExitDelay, CheckpointExitDelay, BoardingExitDelay, VtxoTreeExpiry, RoundMinParticipantsCount, RoundMaxParticipantsCount, VtxoMinAmount, VtxoMaxAmount, UtxoMinAmount, UtxoMaxAmount, SettlementMinExpiryGap, VtxoNoCsvValidationCutoffDate, MaxTxWeight), change NewSettings signature to accept that struct (or a pointer) and construct &Settings from the struct fields, and update all callers to pass a struct literal (or use functional options) instead of positional args so each value is explicit and reviewable.api-spec/protobuf/ark/v1/admin.proto (1)
381-408: Keepupdated_atout of the update payload.
internal/interface/grpc/handlers/adminservice.goignoresreq.settings.updated_at, so reusing the sameSettingsmessage for reads and writes advertises a writable field the server never honors. A dedicated update message keeps the contract accurate and avoids clients round-tripping server-managed data.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@api-spec/protobuf/ark/v1/admin.proto` around lines 381 - 408, The proto reuses message Settings for both reads and writes while server ignores req.settings.updated_at; change the write contract so clients cannot send updated_at by adding a dedicated update message (e.g., message SettingsUpdate or SettingsWritable) that mirrors Settings but omits the updated_at field, then change UpdateSettingsRequest to use that new message instead of Settings; update any generated stubs/clients and ensure adminservice.go handler continues to operate unchanged (and that server-managed updated_at remains set server-side).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@api-spec/openapi/swagger/ark/v1/admin.openapi.json`:
- Around line 1695-1768: The UpdateSettingsRequest/Settings schema must be
updated at the proto/message level to match the handler contract: decide whether
the endpoint is a full-replace or a patch and implement that explicitly—if
full-replace, make UpdateSettingsRequest.settings required and make each
Settings field required (remove writable updatedAt or mark updatedAt as
output/readOnly); if patch, change Settings fields to use nullable wrapper types
(e.g., google.protobuf.Int64Value) or a FieldMask so omitted fields are
distinguishable from zero and won’t overwrite stored values; update the Settings
message to not expose updatedAt as an input field (mark it output-only) and then
regen the OpenAPI/JSON so UpdateSettingsRequest aligns with adminservice's
behavior (refer to UpdateSettingsRequest, Settings, and the adminservice handler
logic).
In `@internal/core/application/admin.go`:
- Around line 634-639: ClearSettings removes persisted settings but doesn't
update the in-memory runtime config; call the settings update handler after
clearing so the node re-seeds defaults and applies the change. Modify
ClearSettings (the function that calls a.repoManager.Settings().Clear) to invoke
a.onSettingsUpdated() (in addition to or instead of a.onInfoChange())
immediately after the clear succeeds so the in-memory settings and any
subscribers are refreshed (Config.loadSettings is the startup equivalent to
emulate).
- Around line 620-630: The UpdateSettings flow currently persists settings
(repoManager.Settings().Upsert) before running live-apply guards, which can lead
to races and invalid persisted values; change UpdateSettings (method
adminService.UpdateSettings) to serialize concurrent updates (add and use a
mutex field on adminService e.g., updateSettingsMu) and perform
validation/live-apply before making durable: acquire the mutex, run the same
validation/startup checks from internal/config/config.go and call
a.onSettingsUpdated(ctx, settings) while still in-memory, only if
validation/onSettingsUpdated succeeds then set settings.UpdatedAt and call
repoManager.Settings().Upsert(ctx, settings), finally release the mutex and call
a.onInfoChange(); propagate errors from validation/apply/upsert unchanged.
- Around line 620-631: UpdateSettings currently writes settings but does not
refresh the cached participant defaults
(roundMinParticipantsCount/roundMaxParticipantsCount), so subsequent
UpdateScheduledSessionConfig calls may still use startup defaults; after
successfully upserting settings and invoking a.onSettingsUpdated, update the
adminService cached fields by assigning a.roundMinParticipantsCount and
a.roundMaxParticipantsCount from the new settings (or call an existing helper
that derives those defaults), ensuring this happens before a.onInfoChange() so
future calls to UpdateScheduledSessionConfig use the new values.
In `@internal/core/application/service.go`:
- Around line 295-302: Add a clarifying doc comment above the toRelativeLocktime
function explaining why values >= minAllowedSequence (512) are treated as
seconds and values < 512 as blocks; mention the BIP68 granularity (512-second
units) and that configuration validation enforces time-based values be multiples
of 512 so this threshold determines whether arklib.RelativeLocktime uses
arklib.LocktimeTypeSecond vs arklib.LocktimeTypeBlock.
In `@internal/infrastructure/db/badger/settings_repo.go`:
- Around line 64-79: The Upsert method in settingsRepository currently retries
on badger.ErrConflict without honoring cancellation; modify Upsert to stop
retrying as soon as ctx is canceled by checking ctx.Done() between attempts and
before sleeping/attempting another r.store.Upsert, returning ctx.Err() if
canceled; preserve existing retry count logic (maxRetries) and continue only
while ctx is active, referencing the Upsert method, settingsKey, maxRetries and
the same badger.ErrConflict check.
In `@internal/infrastructure/db/service.go`:
- Around line 229-232: The settings-store initialization can fail after the DB
connection and earlier repositories are created, leaving those handles open;
wrap resource creation in a cleanup stack or use a defer-until-success pattern:
after creating the DB connection and each repository, push their corresponding
cleanup functions (e.g., db.Close()/Disconnect and repo.Close()/Shutdown) onto a
slice of funcs, and if settingsStoreFactory(...) returns an error, iterate that
slice to call each cleanup before returning the error; apply the same pattern
around the other failing sites noted (the analogous blocks at the later
occurrences referenced) so all partially-initialized resources are closed on
error.
In `@internal/interface/grpc/handlers/adminservice.go`:
- Around line 589-621: The UpdateSettings handler (adminHandler.UpdateSettings)
must validate invariants before calling a.adminService.UpdateSettings: ensure
numeric/duration fields are within acceptable bounds (non-negative for
BanThreshold, BanDuration, UnilateralExitDelay, PublicUnilateralExitDelay,
CheckpointExitDelay, BoardingExitDelay, VtxoTreeExpiry, SettlementMinExpiryGap,
MaxTxWeight), ensure participant counts satisfy RoundMinParticipantsCount <
RoundMaxParticipantsCount, ensure min/max pairs are ordered (VtxoMinAmount <=
VtxoMaxAmount, UtxoMinAmount <= UtxoMaxAmount), and validate any date fields
(VtxoNoCsvValidationCutoffDate) as needed; if any check fails return
status.Error(codes.InvalidArgument, "...") with a clear message. Implement these
checks at the start of adminHandler.UpdateSettings (before calling
a.adminService.UpdateSettings / Upsert) and use the same status codes/messages
pattern as other handlers.
In `@README.md`:
- Around line 102-122: The README admin-settings section is out of sync: update
the documentation to list and describe the newly added endpoints GET
/v1/admin/settings and POST /v1/admin/settings/clear in addition to POST
/v1/admin/settings, and extend the settings table to include the two missing
persisted settings settlement_min_expiry_gap and
vtxo_no_csv_validation_cutoff_date with their descriptions and default values;
ensure the table rows use the same formatting as existing entries (backticked
keys like `settlement_min_expiry_gap`, `vtxo_no_csv_validation_cutoff_date`) and
mention which endpoints manage them.
---
Nitpick comments:
In `@api-spec/protobuf/ark/v1/admin.proto`:
- Around line 381-408: The proto reuses message Settings for both reads and
writes while server ignores req.settings.updated_at; change the write contract
so clients cannot send updated_at by adding a dedicated update message (e.g.,
message SettingsUpdate or SettingsWritable) that mirrors Settings but omits the
updated_at field, then change UpdateSettingsRequest to use that new message
instead of Settings; update any generated stubs/clients and ensure
adminservice.go handler continues to operate unchanged (and that server-managed
updated_at remains set server-side).
In `@internal/core/application/service.go`:
- Around line 304-349: The UpdateSettings method mutates many runtime fields
(e.g., banDuration, banThreshold, unilateralExitDelay,
publicUnilateralExitDelay, checkpointExitDelay, boardingExitDelay, batchExpiry,
roundMinParticipantsCount, roundMaxParticipantsCount, vtxoMinAmount,
vtxoMaxAmount, utxoMinAmount, utxoMaxAmount, settlementMinExpiryGap,
vtxoNoCsvValidationCutoffTime, maxTxWeight, checkpointTapscript, forfeitPubkey)
without synchronization; add a sync.RWMutex (e.g., settingsMu) to the service
struct and wrap the entire UpdateSettings body with settingsMu.Lock()/Unlock(),
and update all readers of those fields (round processing goroutines, any methods
reading these fields) to use settingsMu.RLock()/RUnlock(); alternatively, if
unsynchronized updates are intentional, add a clear comment on UpdateSettings
documenting that it must only be called between rounds and is not
concurrency-safe.
In `@internal/core/domain/settings.go`:
- Around line 25-55: The NewSettings function takes 16 positional int64
arguments which risks silent swaps; replace it with a single explicit config
struct (e.g. SettingsConfig or SettingsOptions) containing named fields matching
Settings (BanThreshold, BanDuration, UnilateralExitDelay,
PublicUnilateralExitDelay, CheckpointExitDelay, BoardingExitDelay,
VtxoTreeExpiry, RoundMinParticipantsCount, RoundMaxParticipantsCount,
VtxoMinAmount, VtxoMaxAmount, UtxoMinAmount, UtxoMaxAmount,
SettlementMinExpiryGap, VtxoNoCsvValidationCutoffDate, MaxTxWeight), change
NewSettings signature to accept that struct (or a pointer) and construct
&Settings from the struct fields, and update all callers to pass a struct
literal (or use functional options) instead of positional args so each value is
explicit and reviewable.
In `@Makefile`:
- Around line 184-200: The run-simulation Makefile target prints and sets unused
MIN and MAX variables which is misleading; remove the MIN and MAX assignments
and their use in the echo so the target only reads CLIENTS and passes
-num-clients to the test. Update the run-simulation recipe's shell block to drop
MIN="$${MIN:-$$CLIENTS}" and MAX="$${MAX:-128}" and change the echo to only
reference $$CLIENTS (leaving the go test invocation that runs
TestBatchSettleMultipleClients unchanged); this aligns the Makefile with the
test in internal/test/e2e/single_batch_smoke_test.go which only parses
-num-clients.
- Around line 191-192: The recipe currently hardcodes ARKD_SESSION_DURATION=60
in the Docker command; make this default-but-overridable by either (a) adding a
Makefile variable declaration like ARKD_SESSION_DURATION ?= 60 and using
ARKD_SESSION_DURATION in the recipe, or (b) using shell expansion in the recipe
ARKD_SESSION_DURATION=${ARKD_SESSION_DURATION:-60} docker compose ... so callers
can run make run-simulation ARKD_SESSION_DURATION=120 to override while keeping
60 as the default.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0ef746c5-f1c4-43ba-8188-e57d581bb56a
⛔ Files ignored due to path filters (4)
api-spec/protobuf/gen/ark/v1/admin.pb.gois excluded by!**/*.pb.go,!**/gen/**api-spec/protobuf/gen/ark/v1/admin.pb.rgw.gois excluded by!**/gen/**api-spec/protobuf/gen/ark/v1/admin_grpc.pb.gois excluded by!**/*.pb.go,!**/gen/**api-spec/protobuf/gen/ark/v1/indexer.pb.rgw.gois excluded by!**/gen/**
📒 Files selected for processing (34)
MakefileREADME.mdapi-spec/openapi/swagger/ark/v1/admin.openapi.jsonapi-spec/protobuf/ark/v1/admin.protodocker-compose.regtest.ymlenvs/arkd.dev.envenvs/arkd.light.envinternal/config/config.gointernal/core/application/admin.gointernal/core/application/admin_test.gointernal/core/application/service.gointernal/core/application/types.gointernal/core/domain/settings.gointernal/core/domain/settings_repo.gointernal/core/ports/repo_manager.gointernal/infrastructure/db/badger/settings_repo.gointernal/infrastructure/db/postgres/migration/20260227000000_add_settings.down.sqlinternal/infrastructure/db/postgres/migration/20260227000000_add_settings.up.sqlinternal/infrastructure/db/postgres/settings_repo.gointernal/infrastructure/db/postgres/sqlc/queries/models.gointernal/infrastructure/db/postgres/sqlc/queries/query.sql.gointernal/infrastructure/db/postgres/sqlc/query.sqlinternal/infrastructure/db/service.gointernal/infrastructure/db/service_test.gointernal/infrastructure/db/sqlite/migration/20260227000000_add_settings.down.sqlinternal/infrastructure/db/sqlite/migration/20260227000000_add_settings.up.sqlinternal/infrastructure/db/sqlite/settings_repo.gointernal/infrastructure/db/sqlite/sqlc/queries/models.gointernal/infrastructure/db/sqlite/sqlc/queries/query.sql.gointernal/infrastructure/db/sqlite/sqlc/query.sqlinternal/interface/grpc/handlers/adminservice.gointernal/interface/grpc/permissions/permissions.gointernal/test/e2e/single_batch_smoke_test.gointernal/test/e2e/utils_test.go
💤 Files with no reviewable changes (2)
- envs/arkd.light.env
- envs/arkd.dev.env
|
addressed the comments from @arkanaai (#939 (comment)) in: 5271785 |
|
Thanks for addressing the feedback, the changes look good overall. A few notes on the commit: ✅ Locktime deduplication — clean, / is the right home. ✅ ClearSettings — now correctly re-seeds defaults and calls ✅ Validate() — covers the critical cases (zero exit delays, min/max participants ordering, public ≥ unilateral). Two small follow-up items:
Neither is a blocker, but worth a quick fix before merge. |
🔍 PR Review: Settings Domain with DB Persistence and Admin CRUD APIReviewer: Arkana (automated) SummaryThis PR moves ~15 operator-configurable parameters (exit delays, VTXO tree expiry, round limits, ban thresholds, amount bounds, etc.) out of environment variables into a new ✅ What looks good
|
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
internal/core/application/admin.go (2)
631-637:⚠️ Potential issue | 🟡 MinorCached participant count fields not refreshed after settings update.
After
UpdateSettingssucceeds,a.roundMinParticipantsCountanda.roundMaxParticipantsCountstill hold their startup values. This affectsUpdateScheduledSessionConfigwhich uses these as fallbacks (lines 279-283, 304-308).Suggested fix
if a.onSettingsUpdated != nil { if err := a.onSettingsUpdated(ctx, settings); err != nil { return err } } + a.roundMinParticipantsCount = settings.RoundMinParticipantsCount + a.roundMaxParticipantsCount = settings.RoundMaxParticipantsCount a.onInfoChange() return nil }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/application/admin.go` around lines 631 - 637, After UpdateSettings succeeds the cached fields a.roundMinParticipantsCount and a.roundMaxParticipantsCount are not being refreshed, so UpdateScheduledSessionConfig still sees old startup values; after the successful call to a.onSettingsUpdated (i.e. inside the same block where you handle the non-nil callback) update the cached values from the new settings (assign settings.RoundMinParticipantsCount and settings.RoundMaxParticipantsCount into a.roundMinParticipantsCount and a.roundMaxParticipantsCount) before calling a.onInfoChange so subsequent UpdateScheduledSessionConfig calls use the updated fallbacks.
623-638:⚠️ Potential issue | 🟠 MajorPersist-then-apply ordering can leave inconsistent state on callback failure.
If
onSettingsUpdatedfails afterUpsertsucceeds, the database contains the new settings while the runtime keeps the old configuration. Without transaction support (per theSettingsRepositoryinterface), consider reversing the order: apply first, persist on success.Suggested reordering
func (a *adminService) UpdateSettings(ctx context.Context, settings domain.Settings) error { if err := settings.Validate(); err != nil { return fmt.Errorf("invalid settings: %w", err) } - settings.UpdatedAt = time.Now() - if err := a.repoManager.Settings().Upsert(ctx, settings); err != nil { - return err - } if a.onSettingsUpdated != nil { if err := a.onSettingsUpdated(ctx, settings); err != nil { return err } } + settings.UpdatedAt = time.Now() + if err := a.repoManager.Settings().Upsert(ctx, settings); err != nil { + return err + } a.onInfoChange() return nil }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/application/admin.go` around lines 623 - 638, UpdateSettings currently persists settings via Settings().Upsert before calling the runtime callback onSettingsUpdated, risking DB/runtime divergence if the callback fails; change the order so the in-memory/runtime application happens first (call a.onSettingsUpdated(ctx, settings) if non-nil), only if that succeeds then call a.repoManager.Settings().Upsert(ctx, settings), and finally call a.onInfoChange(); ensure you still validate and set settings.UpdatedAt before applying, and propagate errors from the callback or the Upsert unchanged so failures abort without leaving the runtime/stored state inconsistent.
🧹 Nitpick comments (2)
internal/core/domain/settings.go (1)
39-69: Consider adding validation forCheckpointExitDelayandMaxTxWeight.The
Validate()method checksUnilateralExitDelay,BoardingExitDelay, andVtxoTreeExpiryfor positive values, but omitsCheckpointExitDelayandMaxTxWeight. For consistency and to prevent invalid configurations:Suggested additions
func (s Settings) Validate() error { if s.UnilateralExitDelay <= 0 { return fmt.Errorf("unilateral exit delay must be greater than 0") } + if s.CheckpointExitDelay <= 0 { + return fmt.Errorf("checkpoint exit delay must be greater than 0") + } if s.BoardingExitDelay <= 0 { return fmt.Errorf("boarding exit delay must be greater than 0") } // ... existing checks ... + if s.MaxTxWeight <= 0 { + return fmt.Errorf("max tx weight must be greater than 0") + } return nil }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/domain/settings.go` around lines 39 - 69, Add validation in Settings.Validate to check CheckpointExitDelay and MaxTxWeight are positive, mirroring existing checks: after the existing UnilateralExitDelay/BoardingExitDelay/VtxoTreeExpiry checks add an if s.CheckpointExitDelay <= 0 { return fmt.Errorf("checkpoint exit delay must be greater than 0") } and an if s.MaxTxWeight <= 0 { return fmt.Errorf("max tx weight must be greater than 0") }, keeping the checks inside the Settings.Validate function.internal/config/config.go (1)
369-371: Consider inliningdetermineLocktimeType.This function is now a simple wrapper around
domain.ToRelativeLocktime. Consider removing the indirection:-func determineLocktimeType(locktime int64) arklib.RelativeLocktime { - return domain.ToRelativeLocktime(locktime) -}Then call
domain.ToRelativeLocktime()directly inloadSettings().🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/config/config.go` around lines 369 - 371, The helper function determineLocktimeType is an unnecessary wrapper around domain.ToRelativeLocktime; remove the determineLocktimeType function and update callers (e.g., loadSettings) to call domain.ToRelativeLocktime(locktime) directly, ensuring any imports or references remain correct and tests/usage of loadSettings continue to compile.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/core/application/admin.go`:
- Around line 640-656: ClearSettings currently does Clear + Upsert before
calling onSettingsUpdated and does not refresh cached participant counts; change
the ordering to match UpdateSettings by: perform
repoManager.Settings().Clear(ctx), set defaults.UpdatedAt, call
a.onSettingsUpdated(ctx, defaults) (if non-nil) before persisting, then Upsert
the defaults, and after successful Upsert refresh the cached participant counts
using the same helper used by UpdateSettings and finally call a.onInfoChange();
ensure you propagate any errors from onSettingsUpdated, the refresh helper, and
Upsert.
In `@internal/core/application/service.go`:
- Around line 295-340: UpdateSettings currently mutates shared service fields
(banDuration, banThreshold, unilateralExitDelay, publicUnilateralExitDelay,
checkpointExitDelay, boardingExitDelay, batchExpiry, roundMinParticipantsCount,
roundMaxParticipantsCount, vtxoMinAmount, vtxoMaxAmount, utxoMinAmount,
utxoMaxAmount, settlementMinExpiryGap, vtxoNoCsvValidationCutoffTime,
maxTxWeight, checkpointTapscript) without synchronization; add a settings mutex
(e.g., s.settingsMu sync.RWMutex) and wrap the entire UpdateSettings body with
s.settingsMu.Lock()/Unlock(), then change readers that access these fields
(examples: startRound, startConfirmation) to use s.settingsMu.RLock()/RUnlock()
when reading to prevent data races and ensure consistent view of settings.
---
Duplicate comments:
In `@internal/core/application/admin.go`:
- Around line 631-637: After UpdateSettings succeeds the cached fields
a.roundMinParticipantsCount and a.roundMaxParticipantsCount are not being
refreshed, so UpdateScheduledSessionConfig still sees old startup values; after
the successful call to a.onSettingsUpdated (i.e. inside the same block where you
handle the non-nil callback) update the cached values from the new settings
(assign settings.RoundMinParticipantsCount and
settings.RoundMaxParticipantsCount into a.roundMinParticipantsCount and
a.roundMaxParticipantsCount) before calling a.onInfoChange so subsequent
UpdateScheduledSessionConfig calls use the updated fallbacks.
- Around line 623-638: UpdateSettings currently persists settings via
Settings().Upsert before calling the runtime callback onSettingsUpdated, risking
DB/runtime divergence if the callback fails; change the order so the
in-memory/runtime application happens first (call a.onSettingsUpdated(ctx,
settings) if non-nil), only if that succeeds then call
a.repoManager.Settings().Upsert(ctx, settings), and finally call
a.onInfoChange(); ensure you still validate and set settings.UpdatedAt before
applying, and propagate errors from the callback or the Upsert unchanged so
failures abort without leaving the runtime/stored state inconsistent.
---
Nitpick comments:
In `@internal/config/config.go`:
- Around line 369-371: The helper function determineLocktimeType is an
unnecessary wrapper around domain.ToRelativeLocktime; remove the
determineLocktimeType function and update callers (e.g., loadSettings) to call
domain.ToRelativeLocktime(locktime) directly, ensuring any imports or references
remain correct and tests/usage of loadSettings continue to compile.
In `@internal/core/domain/settings.go`:
- Around line 39-69: Add validation in Settings.Validate to check
CheckpointExitDelay and MaxTxWeight are positive, mirroring existing checks:
after the existing UnilateralExitDelay/BoardingExitDelay/VtxoTreeExpiry checks
add an if s.CheckpointExitDelay <= 0 { return fmt.Errorf("checkpoint exit delay
must be greater than 0") } and an if s.MaxTxWeight <= 0 { return fmt.Errorf("max
tx weight must be greater than 0") }, keeping the checks inside the
Settings.Validate function.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a1b59888-eb73-40cb-9875-bd5d2eefd762
📒 Files selected for processing (6)
internal/config/config.gointernal/core/application/admin.gointernal/core/application/admin_test.gointernal/core/application/service.gointernal/core/domain/settings.gointernal/interface/grpc/handlers/adminservice.go
🚧 Files skipped from review as they are similar to previous changes (2)
- internal/interface/grpc/handlers/adminservice.go
- internal/core/application/admin_test.go
…x/min participants on ClearSettings, commenting, MaxTxWeight validation, badger settings repo ctx cancellation in retry loop in Upsert
🔍 Follow-up Review — New Commits (
|
|
addressed @arkanaai comment for string based error detction (#939 (comment)) in: 2802d62 however for VtxoTreeExpiry > UnilateralExitDelay while we can normalize the comparison using RelativeLocktime.Seconds(), the block scheduler defaults would fail it (vtxoTreeExpiry=20 blocks ≈ 12,000s vs this is the case that would break things, since now |
|
Makes sense — the block scheduler case is a legitimate exception and you're right that Skipping the Otherwise this looks good to me — both original blockers are resolved. |
🔍 Arkana Review — Follow-up (commit
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
internal/core/application/admin.go (1)
652-673: Note:ClearSettingsapplies to the running service after persisting, unlikeUpdateSettings.In
UpdateSettings,onSettingsUpdatedis called beforeUpsertto fail-fast on live-apply errors. InClearSettings, it's called afterUpsert. IfonSettingsUpdatedwere to fail (unlikely given context snippet 1 shows it just assigns fields), the DB would have the new defaults while the runtime retains old values.Since
service.UpdateSettings(the concrete callback) only performs field assignments and cannot fail in practice, this ordering difference is low-risk. However, for consistency and defensive coding, consider aligning the ordering withUpdateSettings.💡 Optional: Align ordering with UpdateSettings
func (a *adminService) ClearSettings(ctx context.Context) error { a.settingsMu.Lock() defer a.settingsMu.Unlock() if err := a.repoManager.Settings().Clear(ctx); err != nil { return err } defaults := a.defaultSettings defaults.UpdatedAt = time.Now() + if a.onSettingsUpdated != nil { + if err := a.onSettingsUpdated(ctx, defaults); err != nil { + return err + } + } if err := a.repoManager.Settings().Upsert(ctx, defaults); err != nil { return err } - if a.onSettingsUpdated != nil { - if err := a.onSettingsUpdated(ctx, defaults); err != nil { - return err - } - } a.roundMinParticipantsCount = defaults.RoundMinParticipantsCount🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/application/admin.go` around lines 652 - 673, The ClearSettings workflow currently calls repoManager.Settings().Upsert(...) before invoking a.onSettingsUpdated(...), risking the DB being updated while the runtime fails to apply live changes; change ClearSettings to mirror UpdateSettings by calling a.onSettingsUpdated(ctx, defaults) before attempting a.repoManager.Settings().Upsert(ctx, defaults) so failures in onSettingsUpdated cause an early return and avoid persisting inconsistent state (keep locking behavior with a.settingsMu and leave subsequent assignments to a.roundMinParticipantsCount/a.roundMaxParticipantsCount and a.onInfoChange() as-is).internal/core/domain/settings.go (1)
53-88: Consider adding validation for amount min/max ordering when both are set.The
Validate()method checks several constraints but doesn't validate thatVtxoMinAmount <= VtxoMaxAmountandUtxoMinAmount <= UtxoMaxAmountwhen both are explicitly set (i.e., not-1). From the context,-1appears to mean "unset/unlimited", so validation should only apply when both values are positive.💡 Optional enhancement
if s.MaxTxWeight <= 0 { return &ErrInvalidSettings{"max tx weight must be greater than 0"} } + if s.VtxoMinAmount > 0 && s.VtxoMaxAmount > 0 && s.VtxoMinAmount > s.VtxoMaxAmount { + return &ErrInvalidSettings{"vtxo min amount must be <= vtxo max amount"} + } + if s.UtxoMinAmount > 0 && s.UtxoMaxAmount > 0 && s.UtxoMinAmount > s.UtxoMaxAmount { + return &ErrInvalidSettings{"utxo min amount must be <= utxo max amount"} + } return nil🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/core/domain/settings.go` around lines 53 - 88, The Validate() method on Settings lacks checks ensuring min ≤ max for amount pairs; add validation in Settings.Validate to verify when both VtxoMinAmount and VtxoMaxAmount are set (i.e., != -1) that VtxoMinAmount <= VtxoMaxAmount and similarly when both UtxoMinAmount and UtxoMaxAmount are set that UtxoMinAmount <= UtxoMaxAmount, returning &ErrInvalidSettings with clear messages like "vtxo min amount must be <= vtxo max amount" and "utxo min amount must be <= utxo max amount" when the checks fail.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/core/domain/settings.go`:
- Around line 26-31: Add explicit upper-bound checks in Settings.Validate to
ensure delay fields do not overflow uint32: validate that UnilateralExitDelay,
BoardingExitDelay, and VtxoTreeExpiry are each <= math.MaxUint32 and return a
descriptive ErrInvalidSettings (e.g., "unilateral exit delay exceeds maximum
uint32 value") on violation; this prevents the silent truncation when
ToRelativeLocktime casts an int64 to uint32.
---
Nitpick comments:
In `@internal/core/application/admin.go`:
- Around line 652-673: The ClearSettings workflow currently calls
repoManager.Settings().Upsert(...) before invoking a.onSettingsUpdated(...),
risking the DB being updated while the runtime fails to apply live changes;
change ClearSettings to mirror UpdateSettings by calling
a.onSettingsUpdated(ctx, defaults) before attempting
a.repoManager.Settings().Upsert(ctx, defaults) so failures in onSettingsUpdated
cause an early return and avoid persisting inconsistent state (keep locking
behavior with a.settingsMu and leave subsequent assignments to
a.roundMinParticipantsCount/a.roundMaxParticipantsCount and a.onInfoChange()
as-is).
In `@internal/core/domain/settings.go`:
- Around line 53-88: The Validate() method on Settings lacks checks ensuring min
≤ max for amount pairs; add validation in Settings.Validate to verify when both
VtxoMinAmount and VtxoMaxAmount are set (i.e., != -1) that VtxoMinAmount <=
VtxoMaxAmount and similarly when both UtxoMinAmount and UtxoMaxAmount are set
that UtxoMinAmount <= UtxoMaxAmount, returning &ErrInvalidSettings with clear
messages like "vtxo min amount must be <= vtxo max amount" and "utxo min amount
must be <= utxo max amount" when the checks fail.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a2021358-40fc-4c29-9846-8fa56c78afc1
📒 Files selected for processing (3)
internal/core/application/admin.gointernal/core/domain/settings.gointernal/interface/grpc/handlers/adminservice.go
|
for the commnets by @arkanaai in: #939 (comment) they were addressed in commit: 557ccad |
|
Thanks. The changes in 557ccad look good:
One small thing: No blockers from me on this PR. |
🔍 Arkana Review — Commit
|
| | Endpoint | Method | Description | | ||
| |-----------------------------------|--------|------------------------------------------| | ||
| | `/v1/admin/settings` | GET | Retrieve current settings | | ||
| | `/v1/admin/settings` | POST | Update settings (full replace) | |
There was a problem hiding this comment.
isnt this endpoint partial update? since every field is optional?
| | `/v1/admin/settings` | POST | Update settings (full replace) | | |
| | `/v1/admin/settings` | POST | Update settings (partial; only provided fields change) | |
| ### Configuration Options | ||
|
|
||
| The `arkd` server can be configured using environment variables. | ||
| The `arkd` server can be configured using environment variables and the admin settings API. |
There was a problem hiding this comment.
the env vars are only used for seeding on first boot. I know we say that on line 110 in this file, but the reader is going to read this line before line 110. I think we need to also call that out here, that these vars are for seeding, and that each susequent boot of arkd the runtime reads the settings from the db and not these env vars
There was a problem hiding this comment.
whats unclear to me is that we do not surface a comprehensize list of the first boot seed variables. having a list of these would make it clear what gets set to what right out of the box
| } | ||
| txid := arkPtx.UnsignedTx.TxID() | ||
|
|
||
| settings, err := s.cache.Settings().Get(ctx) |
There was a problem hiding this comment.
theres a handful of cases in this file where we call s.cache.Settings().Get(ctx) and then set a bunch of local variables from the yielded settings. It feels like something that should go in its own function and have a corresponding settings struct. For example in this SubmitOffChainTx we could call a new function:
settings, err := GetSubmitOffChainTxSettings(ctx)
func (s *service) GetSubmitOffChainTxSettings(ctx context.Context) (settings OffChainTxSettings, err error) {
settings, err := s.cache.Settings().Get(ctx)
if err != nil {
return nil, errors.INTERNAL_ERROR.New("failed to get settings: %w", err)
}
settings: OffChainTxSettings{
banThreshold: settings.BanThreshold,
minAllowedExitDelay: settings.UnilateralExitDelay,
vtxoNoCsvValidationCutoffDate: settings.VtxoNoCsvValidationCutoffDate,
signerPubkey: settings.SignerPubkey,
vtxoMinAmount: settings.VtxoMinAmount,
vtxoMaxAmount: settings.VtxoMaxAmount,
checkpointTapscript: settings.CheckpointTapscript,
maxOpReturnOutputs: settings.MaxOpReturnOutputs,
maxTxWeight: settings.MaxTxWeight,
maxAssetsPerVtxo: settings.MaxAssetsPerVtxo(),
allowCSVBlockType: settings.AllowCSVBlockType(),
}
return settings, nil
}
| MaxOpReturnOutputs: int64(s.MaxOpReturnOutputs), | ||
| } | ||
| // nolint | ||
| buf, _ := json.Marshal(data) |
There was a problem hiding this comment.
swallowing the error, our prior digest construction code did not swallow the error. I think we need to return the error and upstream where this Digest() function is called we will need to handle the error
|
I summoned the AI who actually had some valid concerns: |
| periodSec, durationSec int64 | ||
| roundMin, roundMax int64 | ||
| ) | ||
| ssErr := tx.QueryRowContext(ctx, ` |
There was a problem hiding this comment.
can we turn this into a generated query from query.sql
There was a problem hiding this comment.
well, actually this and other queries were still in query.sql, and i dropped them because they are queries related to tables that are not there anymore.
- Guard the admin read-modify-write settings flows (UpdateSettings and the scheduled-session/batch-fees mutators) with a mutex and dispatch the cache refresh synchronously, so concurrent updates can't lose each other or leave the cache behind the DB. - Validate vtxo/utxo max >= min amounts and require max op return outputs > 0.
closes #934
variables and into the database
Summary by CodeRabbit
New Features
Documentation
Chores
Tests